use crate::{
    common,
    tables::menu::{Menu, TbMenu},
};
use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
use once_cell::sync::Lazy;
use rbatis::rbdc;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{collections::HashMap, sync::RwLock};

static MENU_CACHE: Lazy<RwLock<HashMap<u64, TbMenu>>> = Lazy::new(|| RwLock::new(HashMap::new()));

pub async fn init_menus() -> Result<(), rbdc::Error> {
    let menus = TbMenu::get_all_menu().await?;

    let menu_cache = &mut MENU_CACHE.write().unwrap();
    menus.into_iter().for_each(|m| {
        menu_cache.insert(m.id.unwrap(), m);
    });

    Ok(())
}

async fn insert_menu(data: &mut TbMenu) -> Result<(), rbdc::Error> {
    TbMenu::insert_menu(data).await?;

    let menu_cache = &mut MENU_CACHE.write().unwrap();
    menu_cache.insert(data.id.unwrap(), data.to_owned());

    Ok(())
}

async fn update_menu(data: &TbMenu) -> Result<(), rbdc::Error> {
    TbMenu::update_menu(data).await?;

    let menu_cache = &mut MENU_CACHE.write().unwrap();
    menu_cache.insert(data.id.unwrap(), data.to_owned());

    Ok(())
}

async fn delete_menu(id: u64) -> Result<(), rbdc::Error> {
    TbMenu::delete_menu(id).await?;

    let menu_cache = &mut MENU_CACHE.write().unwrap();
    menu_cache.remove(&id);

    Ok(())
}

fn has_children(id: u64) -> bool {
    let menu_cache = &MENU_CACHE.read().unwrap();
    menu_cache.iter().any(|(_, v)| v.parent_id == Some(id))
}

pub fn get_all_menu(hidden: bool) -> Vec<Menu> {
    let menus = MENU_CACHE.read().unwrap();
    let menus: Vec<Menu> = menus.values().map(|m| Menu::from(m.clone())).collect();

    let mut effect_menus: Vec<Menu> = menus
        .iter()
        .filter(|m| m.parent_id.is_none() && (if hidden { !m.hidden } else { true }))
        .cloned()
        .collect();

    fn get_routes(parent: &mut Menu, left_menus: &Vec<Menu>, hidden: bool) {
        let select_menus: Vec<Menu> = left_menus
            .iter()
            .filter(|m| m.parent_id == parent.id && (if hidden { !m.hidden } else { true }))
            .cloned()
            .collect();
        if !select_menus.is_empty() {
            parent.routes = Some(select_menus);
            if let Some(m) = parent.routes.as_mut() {
                m.iter_mut().for_each(|m| get_routes(m, left_menus, hidden))
            }
        }
    }

    effect_menus
        .iter_mut()
        .for_each(|m| get_routes(m, &menus, hidden));

    fn sort(menus: &mut [Menu]) {
        menus.sort_by(|a, b| a.sort.cmp(&b.sort));
        menus.iter_mut().for_each(|m| {
            if let Some(m) = m.routes.as_mut() {
                sort(m)
            }
        });
    }

    sort(&mut effect_menus);

    effect_menus
}

pub fn get_role_menus(role_id: u64) -> Option<Vec<Menu>> {
    let mut role_menus = get_all_menu(true);

    let role = super::role::get_role(role_id)?;

    let menu_ids = role.menu_ids.as_ref()?;

    fn fix(menus: &mut Vec<Menu>, menu_ids: &[u64]) {
        menus.retain(|m| menu_ids.contains(&m.id.unwrap()));
        menus.iter_mut().for_each(|m| {
            if let Some(m) = m.routes.as_mut() {
                fix(m, menu_ids)
            }
        });
    }

    fix(&mut role_menus, menu_ids);

    Some(role_menus)
}

#[get("")]
async fn get() -> impl Responder {
    common::ResData::<Menu>::from_data(Ok(get_all_menu(false))).resp_json()
}

fn check_menu(data: &web::Json<Menu>) -> Result<(), &str> {
    if data.path.is_empty() {
        return Err("路由路径不能为空");
    }

    if data.name.is_empty() {
        return Err("路由名称不能为空");
    }

    if data.title.is_empty() {
        return Err("菜单名称不能为空");
    }

    Ok(())
}

#[post("")]
async fn post(data: web::Json<Menu>) -> impl Responder {
    let check = check_menu(&data);
    if let Err(e) = check {
        return HttpResponse::Ok().json(json!({
            "success": false,
            "errorMessage": e
        }));
    }

    HttpResponse::Ok().json(match insert_menu(&mut data.0.into()).await {
        Ok(_) => json!({
            "success": true,
            "errorMessage": "操作成功"
        }),
        Err(e) => json!({
            "success": false,
            "errorMessage": e.to_string().as_str()
        }),
    })
}

#[put("")]
async fn put(data: web::Json<Menu>) -> impl Responder {
    let check = check_menu(&data);
    if let Err(e) = check {
        return HttpResponse::Ok().json(json!({
            "success": false,
            "errorMessage": e
        }));
    }

    HttpResponse::Ok().json(match update_menu(&data.0.into()).await {
        Ok(_) => json!({
            "success": true,
            "errorMessage": "操作成功"
        }),
        Err(e) => json!({
            "success": false,
            "errorMessage": e.to_string().as_str()
        }),
    })
}

#[derive(Debug, Serialize, Deserialize)]
struct Delete {
    id: u64,
}

#[delete("")]
async fn delete(data: web::Json<Delete>) -> impl Responder {
    if has_children(data.id) {
        return HttpResponse::Ok().json(json!({
            "success": false,
            "errorMessage": "不能删除有子菜单的菜单"
        }));
    }

    HttpResponse::Ok().json(match delete_menu(data.id).await {
        Ok(_) => json!({
            "success": true,
            "errorMessage": "操作成功"
        }),
        Err(e) => json!({
            "success": false,
            "errorMessage": e.to_string().as_str()
        }),
    })
}

pub fn routes(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/menu")
            .service(get)
            .service(post)
            .service(put)
            .service(delete),
    );
}

#[cfg(test)]
pub mod tests {
    use crate::{
        handlers::menu::{self, get_role_menus},
        tables::menu::TbMenu,
        utils::mysql,
    };
    use actix_web::{test, App};
    use serde_json::{json, Value};

    #[actix_web::test]
    async fn test_get_menus() {
        mysql::init_pool().await;

        super::init_menus().await.expect("init menus error");

        let app = test::init_service(App::new().configure(menu::routes)).await;

        let req = test::TestRequest::get().uri("/menu").to_request();

        let resp = test::call_service(&app, req).await;
        let status = resp.status();
        println!("resp status is {:?}", status);
        let resp_body = test::read_body(resp).await;
        let json_value: Value = serde_json::from_slice(&resp_body).unwrap();
        println!("resp body is {}", json_value);
        assert!(status.is_success());
    }

    #[actix_web::test]
    async fn test_get_all_menu() {
        mysql::init_pool().await;

        println!("menu vec is {:?}", TbMenu::get_all_menu().await);
    }

    #[actix_web::test]
    async fn test_get_role_menus() {
        mysql::init_pool().await;

        super::init_menus().await.expect("init menus error");
        super::super::role::init_roles()
            .await
            .expect("init roles error");

        println!(
            "menu vec is {:?}",
            serde_json::to_string(&get_role_menus(1).unwrap())
        );
    }

    #[actix_web::test]
    async fn test_add_menu() {
        mysql::init_pool().await;

        super::init_menus().await.expect("init menus error");

        let app = test::init_service(App::new().configure(menu::routes)).await;

        let req = test::TestRequest::post()
            .uri("/menu")
            .set_json(json!({"path": "/test", "name": "test", "title": "测试", "hidden": false, "sort": 99}))
            .to_request();

        let resp = test::call_service(&app, req).await;
        let status = resp.status();
        println!("resp status is {:#?}", status);
        let resp_body = test::read_body(resp).await;
        let json_value: Value = serde_json::from_slice(&resp_body).unwrap();
        println!("resp body is {:#?}", json_value);
        assert!(status.is_success());
    }

    #[actix_web::test]
    async fn test_update_menu() {
        mysql::init_pool().await;

        super::init_menus().await.expect("init menus error");

        let app = test::init_service(App::new().configure(menu::routes)).await;

        let req = test::TestRequest::put()
            .uri("/menu")
            .set_json(json!({"id": 15, "path": "/test2", "name": "test", "title": "测试", "hidden": false, "sort": 99}))
            .to_request();

        let resp = test::call_service(&app, req).await;
        let status = resp.status();
        println!("resp status is {:#?}", status);
        let resp_body = test::read_body(resp).await;
        let json_value: Value = serde_json::from_slice(&resp_body).unwrap();
        println!("resp body is {:#?}", json_value);
        assert!(status.is_success());
    }

    #[actix_web::test]
    async fn test_delete_menu() {
        mysql::init_pool().await;

        super::init_menus().await.expect("init menus error");

        let app = test::init_service(App::new().configure(menu::routes)).await;

        let req = test::TestRequest::delete()
            .uri("/menu")
            .set_json(json!({"id": 16}))
            .to_request();

        let resp = test::call_service(&app, req).await;
        let status = resp.status();
        println!("resp status is {:#?}", status);
        let resp_body = test::read_body(resp).await;
        let json_value: Value = serde_json::from_slice(&resp_body).unwrap();
        println!("resp body is {:#?}", json_value);
        assert!(status.is_success());
    }
}
